package org.jeecg.modules.demo.socket.until;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.codec.serialization.ObjectEncoder;
import io.netty.handler.stream.ChunkedWriteHandler;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.net.InetSocketAddress;

/**
 * @author sixiaojie
 * @date 2020-03-28-13:44
 */

@Component
@Slf4j
public class NettyServer{

  /**
   * webSocket协议名
   */
  private static final String WEBSOCKET_PROTOCOL = "WebSocket";

  /**
   * 端口号
   */
  @Value("${webSocket.netty.port:58080}")
  private int port;

  /**
   * webSocket路径
   */
  @Value("${webSocket.netty.path:/webSocket}")
  private String webSocketPath;

  @Autowired
  private WebSocketHandler webSocketHandler;

  private EventLoopGroup bossGroup;
  private EventLoopGroup workGroup;

  /**
   * 启动
   * @throws InterruptedException
   */
  private void start() throws InterruptedException {
    log.info("运行到NettyServer的start()");
    /*
    NioEventLoopGroup是一个可处理I/O操作的多线程事件循环。Netty提供了多种EventLoopGroup的实现用于不同类型的传输。
    在之前的例子中，实现了简单的客户端应用，并且使用了两个NioEventLoopGroup，通常第一个叫做boss接受收到的连接请求。第二
    个通常叫做worker，只要boss接受了连接请求并将注册连接请求到worker，它就会处理接收连接的传输。
    ServerBootstrap也继承AbstractBootstrap,所以ServerBootstrap和Bootstrap的结构是差不多的,
    但是ServerBootstrap负责初始化netty服务器，并且开始监听端口的socket请求。
     */
    bossGroup = new NioEventLoopGroup();
    workGroup = new NioEventLoopGroup();
    ServerBootstrap bootstrap = new ServerBootstrap();
    // bossGroup辅助客户端的tcp连接请求, workGroup负责与客户端之间的读写操作
    bootstrap.group(bossGroup,workGroup);//Bootstrap客户端启动辅助对象
    // 设置NIO类型的channel
    bootstrap.channel(NioServerSocketChannel.class);//通道
    // 设置监听端口
    bootstrap.localAddress(new InetSocketAddress(port));
    // 连接到达时会创建一个通道
    bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
      @Override
      protected void initChannel(SocketChannel ch) throws Exception {
        // 流水线管理通道中的处理程序（Handler），用来处理业务
        // webSocket协议本身是基于http协议的，所以这边也要使用http编解码器
        ch.pipeline().addLast(new HttpServerCodec());
        ch.pipeline().addLast(new ObjectEncoder());
        // 以块的方式来写的处理器
        ch.pipeline().addLast(new ChunkedWriteHandler());
        /*
        说明：
        1、http数据在传输过程中是分段的，HttpObjectAggregator可以将多个段聚合
        2、这就是为什么，当浏览器发送大量数据时，就会发送多次http请求
         */
        ch.pipeline().addLast(new HttpObjectAggregator(8192));
        /*
        说明：
        1、对应webSocket，它的数据是以帧（frame）的形式传递
        2、浏览器请求时 ws://localhost:58080/xxx 表示请求的uri
        3、核心功能是将http协议升级为ws协议，保持长连接
        */
        ch.pipeline().addLast(new WebSocketServerProtocolHandler(webSocketPath, WEBSOCKET_PROTOCOL, true, 65536 * 10));
        // 自定义的handler，处理业务逻辑
        ch.pipeline().addLast(webSocketHandler);
      }
    });
    // 配置完成，开始绑定server，通过调用sync同步方法阻塞直到绑定成功
    ChannelFuture channelFuture = bootstrap.bind().sync();//代表I/O操作的执行结果，通过事件机制，获取执行结果，通过添加监听器，执行我们想要的操作
    log.info("Server started and listen on:{}",channelFuture.channel().localAddress());
    // 对关闭通道进行监听
    channelFuture.channel().closeFuture().sync();
  }

  /**
   * 释放资源
   * @throws InterruptedException
   * 在开发中我们如果要在关闭spring容器后释放一些资源,通常的做法有如下几种:
   * 1.在方法上加上@PreDestroy注解
   * 2.实现DisposableBean接口,实现其destroy方法
   */
  @PreDestroy
  public void destroy() throws InterruptedException {
    log.info("运行到NettyServer的destroy()");
    if(bossGroup != null){
      bossGroup.shutdownGracefully().sync();
    }
    if(workGroup != null){
      workGroup.shutdownGracefully().sync();
    }
  }

  /*
  被@PostConstruct修饰的方法会在服务器加载Servle的时候运行，并且只会被服务器执行一次。
  PostConstruct在构造函数之后执行,init()方法之前执行。PreDestroy（）方法在destroy()方法执行执行之后执行
   */
  @PostConstruct()
  public void init() {
    log.info("运行到NettyServer的init()");
    //需要开启一个新的线程来执行netty server 服务器
    new Thread(() -> {
      try {
        start();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }).start();
  }
}